package xtreamclient


import freemarker.cache.ClassTemplateLoader
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.application.log
import io.ktor.features.CallLogging
import io.ktor.features.DefaultHeaders
import io.ktor.freemarker.FreeMarker
import io.ktor.freemarker.FreeMarkerContent
import io.ktor.http.HttpStatusCode
import io.ktor.http.URLBuilder
import io.ktor.http.content.files
import io.ktor.http.content.static
import io.ktor.http.content.staticRootFolder
import io.ktor.request.receiveParameters
import io.ktor.response.respond
import io.ktor.response.respondRedirect
import io.ktor.routing.Routing
import io.ktor.routing.get
import io.ktor.routing.post
import io.ktor.routing.route
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.slf4j.LoggerFactory
import xtreamclient.api.*
import xtreamclient.util.BufferedAsync
import java.awt.*
import java.io.File
import java.lang.IllegalStateException
import java.net.URI
import java.text.SimpleDateFormat
import java.time.Duration
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import javax.swing.ImageIcon
import kotlin.system.exitProcess


val log = LoggerFactory.getLogger("Main")
fun main_retrofit() {
    val propertyHandler = PropertyHandler()
    val props = propertyHandler.loadProperties("properties.json")

    val service = Xtream.loadService(props.servers.first())
    runBlocking {
        val feedback = service.authenticate()

        val categories = service.getLiveStreamCategories()

        val streams = service.getStreams(categories[4].categoryId)
        println(streams)
    }
}



suspend fun loadEPGs(services:List<XtreamService>):List<TV> {
    val epg = services.map { it.getCompleteEPG() }
    println("EPGs loaded")
    return epg
}



fun initializeSystemTray(vlcHandler: HTTPVLCHandler) {
    if(SystemTray.isSupported()) {
        val popup = PopupMenu()

        val image = ImageIcon(VLCHandler::class.java.classLoader.getResource("img/logo.gif"),"tray icon")
        val trayIcon = TrayIcon(image.image)
        val tray = SystemTray.getSystemTray()

        // Create a pop-up menu components

        // Create a pop-up menu components

        val openBrowserItem = MenuItem("Open Console")
        val restartVLCItem = MenuItem("(Re-)start VLC")
        val exitItem = MenuItem("Exit")
        openBrowserItem.addActionListener {
            if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
                Desktop.getDesktop().browse(URI("http://localhost:$serverPort"));
            }
        }
        restartVLCItem.addActionListener { vlcHandler.restartVLC() }
        exitItem.addActionListener {
            tray.remove(trayIcon)
            vlcHandler.closeVLC()
            exitProcess(0) }

        //Add components to pop-up menu

        //Add components to pop-up menu
        popup.add(openBrowserItem)
        popup.add(restartVLCItem)
        popup.add(exitItem)

        trayIcon.popupMenu = popup

        try {
            tray.add(trayIcon)
        } catch (e: AWTException) {
            println("TrayIcon could not be added.")
        }
    }
}

fun Application.module() {
    val propertyHandler = PropertyHandler()
    val props = propertyHandler.loadProperties("properties.json")
    var nsp = propertyHandler.getNamedServerProperties()

    var services = nsp?.map { Xtream.loadService(it.properties) }
    val vlcHandler = HTTPVLCHandler()
    vlcHandler.connect()

    val categoryCache = mutableMapOf<Int,List<MediaCategory>>()

    //val epgs = services?.let { async { loadEPGs(services) }}


    initializeSystemTray(vlcHandler)

    val epginitializer  = { -> BufferedAsync<List<TV>?> (this, Duration.ofMinutes(35)) {
        log.debug("Loading EPG")
        if(services != null){
            services?.let{ it -> loadEPGs(it)}
        } else {
            null
        }
    }}
    var epgs = epginitializer()

    install(FreeMarker) {
        templateLoader = ClassTemplateLoader(this::class.java.classLoader, "templates")
    }
    install(DefaultHeaders)
    install(CallLogging)
    install(Routing) {
        get("/favicon.ico") {
            call.respond(HttpStatusCode.NotFound)
        }
        get("/") {
            //call.respond(FreeMarkerContent("index.ftl", mapOf("data" to (listOf(1, 2, 3))), ""))
            call.respond(FreeMarkerContent("index.ftl", mapOf("servers" to nsp!!),""))
        }
        get("/api/restartVLC") {
            vlcHandler.restartVLC()
            call.respond("restarting VLC")
        }
        get("/reloadConfig") {
            propertyHandler.loadProperties("properties.json")
            nsp = propertyHandler.getNamedServerProperties()
            services = nsp?.map { Xtream.loadService(it.properties) }
            epgs = epginitializer()
            call.respondRedirect("/")
        }
        post("/addServer") {
            val post = call.receiveParameters()
            val playlistURL = post["newServerURL"]?.let { URLBuilder(it).build() }
            if(playlistURL!= null && playlistURL.parameters.contains("username") && playlistURL.parameters.contains("password")) {
                val property = ServerProperties(playlistURL.protocol.name+"://"+playlistURL.host,
                    playlistURL.port,
                    playlistURL.parameters["username"]?:"",
                    playlistURL.parameters["password"]?:"")
                propertyHandler.addServerProperties(property)
                propertyHandler.writeProperties("properties.json")
                nsp = propertyHandler.getNamedServerProperties()
                services = nsp?.map { Xtream.loadService(it.properties) }
                epgs = epginitializer()
                call.respondRedirect("/")
            } else {
                call.respond(HttpStatusCode.InternalServerError,"Playlist URL incorrect")
            }
        }
        route("/{serverid}") {

            get("/") {
                val id = call.parameters["serverid"]?.toIntOrNull()?:0
                runBlocking {
                    val service = services?.getOrNull(id)
                    try {
                        val cats = service?.getLiveStreamCategories()

                        val movieCategories = service?.getVODCategories()
                        val series = service?.getSeries()?.sortedBy { it.name }
                        if(cats!=null) {
                            categoryCache.put(id,cats)
                            call.respond(FreeMarkerContent("server_overview.ftl", mapOf("categories" to cats,
                                "movieCats" to movieCategories, "series" to series)))
                        } else {
                            call.respond(HttpStatusCode.Forbidden,"Wrong id")
                        }
                    } catch (e:IllegalStateException) {
                        try{
                            val player = service?.authenticate()
                            if(player?.userInfo?.status=="Expired") {
                                call.respond("User is expired")
                            }
                        } catch(e:Exception){
                            call.respond(HttpStatusCode.Forbidden,"Cannot establish connection to server. Error is ${e.message}")
                        }

                    }

                }
            }
            get("/remove") {
                val id = call.parameters["serverid"]?.toIntOrNull()?:0
                val service = nsp?.getOrNull(id)
                call.respond(FreeMarkerContent("confirmdelete.ftl", mapOf("server" to service,"id" to id)))
            }
            get("/remove/confirm") {
                val id = call.parameters["serverid"]?.toIntOrNull()?:0
                val service = nsp?.getOrNull(id)
                service?.let {
                    val sdf = DateTimeFormatter.ofPattern("yyy-MM-dd_HH-mm")
                    propertyHandler.writeProperties("properties-backup-${LocalDateTime.now().format(sdf)}")
                    propertyHandler.removeServerProperties(service.properties)
                    propertyHandler.writeProperties("properties.json")
                    nsp = propertyHandler.getNamedServerProperties()
                    services = nsp?.map { Xtream.loadService(it.properties) }

                }
                call.respondRedirect("/")

            }
            get("/category/{catid}") {
                val serverId = call.parameters["serverid"]?.toIntOrNull()?:0
                val catId = call.parameters["catid"]
                catId?.let {

                    val streams = services?.getOrNull(serverId)?.getStreams(it)
                    val server = nsp?.getOrNull(serverId)?.properties
                    //val epg = epgs?.completedOrNull()?.getOrNull(serverId)

                    val category = categoryCache.get(serverId)?.find { cat -> cat.categoryId == it }
                    val epg = epgs.getOrNull()?.getOrNull(serverId)
                    val currentShows = streams?.map { stream ->
                        val thisStreamProgrammes = epg?.programmes?.filter { programme ->
                            programme.channel == stream.epgChannelId }
                        val currentProgramme = thisStreamProgrammes?.find { programme ->
                            programme.startDate.isBefore(ZonedDateTime.now()) &&
                                    programme.endDate.isAfter(ZonedDateTime.now())
                        }
                        val nextProgramme = thisStreamProgrammes?.sortedBy { it.startDate }
                            ?.find { it.startDate.isAfter(ZonedDateTime.now()) }
                        LiveStreamWithCurrentProgramme(stream,currentProgramme,nextProgramme)
                    }
                    call.respond(FreeMarkerContent("stream_list.ftl",
                        mapOf("streams" to currentShows,
                            "server" to serverId,
                            "servername" to server?.hostname,
                            "categoryname" to (category?.categoryName?:"Unknown"))))
                }
            }
            get("/movie-category/{catid}") {
                val serverId = call.parameters["serverid"]?.toIntOrNull()?:0
                val catId = call.parameters["catid"]
                catId?.let {
                    val streams = services?.getOrNull(serverId)?.getVODStreams(catId)
                    val server = nsp?.getOrNull(serverId)?.properties
                    call.respond(FreeMarkerContent("movie_list.ftl",
                        mapOf("streams" to streams,
                            "server" to serverId,
                            "servername" to server?.hostname,
                            "categoryname" to "Unknown")))
                }
            }
            get("/series-category/{catid}") {
                val serverId = call.parameters["serverid"]?.toIntOrNull()?:0
                val catId = call.parameters["catid"]
                catId?.let {
                    val series = services?.getOrNull(serverId)?.getSeriesOverview(catId)
                    val server = nsp?.getOrNull(serverId)?.properties
                    call.respond(FreeMarkerContent("series_overview.ftl",
                        mapOf("series" to series,
                            "server" to serverId,
                            "servername" to server?.hostname,
                            "categoryname" to "Unknown")))
                }
            }
            get("/play/{streamid}") {
                val serverId = call.parameters["serverid"]?.toIntOrNull()?:0
                val streamId = call.parameters["streamid"]
                streamId?.let {
                    val server = nsp?.getOrNull(serverId)?.properties

                    val streamUrl = server?.getLiveStreamBase()+streamId+".ts"
                    println("Sending url:$streamUrl")
                    vlcHandler.playItem(streamUrl)
                }
            }
            get("/play-movie/{streamid}") {
                val serverId = call.parameters["serverid"]?.toIntOrNull()?:0
                val streamId = call.parameters["streamid"]
                streamId?.let {
                    val server = nsp?.getOrNull(serverId)?.properties

                    val streamUrl = server?.getMovieStreamBase()+streamId+".mkv"
                    println("Sending url:$streamUrl")
                    vlcHandler.playItem(streamUrl)
                }
            }
            get("/play-series/{streamid}") {
                val serverId = call.parameters["serverid"]?.toIntOrNull()?:0
                val streamId = call.parameters["streamid"]
                streamId?.let {
                    val server = nsp?.getOrNull(serverId)?.properties

                    val streamUrl = server?.getSeriesStreamBase()+streamId+".mkv"
                    println("Sending url:$streamUrl")
                    vlcHandler.playItem(streamUrl)
                }
            }
            get("/play-timeshift/{streamid}/{startTime}/{duration}") {
                val serverId = call.parameters["serverid"]?.toIntOrNull()?:0
                val streamId = call.parameters["streamid"]
                val startTime = call.parameters["startTime"]
                val duration = call.parameters["duration"]
                streamId?.let {
                    val server = nsp?.getOrNull(serverId)?.properties
                    println(services?.getOrNull(serverId)?.authenticate())
                    val streamUrl = server?.getTimeshiftStreamBase()+duration+"/"+startTime+"/"+streamId+".rtmp"
                    println("Sending url:$streamUrl")
                    vlcHandler.playItem(streamUrl)
                }
            }
            get("/epg") {
                val serverId = call.parameters["serverid"]?.toIntOrNull()?:0
                val epg = services?.getOrNull(serverId)?.getCompleteEPG()
                call.respond("Found EPG, channels: ${epg?.channels?.size}")
            }

        }
        static("static") {
            staticRootFolder = File("static")
            println("StaticFolder")
            files("css")
            files("js")
        }
    }

}
val serverPort = 8080
fun main() {
    embeddedServer(Netty, serverPort,watchPaths = listOf("MainKt","xtream-vlc-client"),module = Application::module).start()
}

